﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace ImageProcessing
{

	//This is intended to take the place of the ColorConversions class
	public static class BitmapConversions
	{
		private static double[,] adRGBtoYIQ = new double[,] {
			{0.298895,0.586622,0.114482},
			{.595716, -.274453, -.321263},
			{.211456, -.522591, .311135}
		};

		private static double[,] adYIQtoRGB = new double[,] { 
			{1, .9563, .6210},
			{1, -.2721, -.6474},
			{1, -1.1070,1.7046}
		};

		private static double[,] adPitasRGBtoHSI = new double[,] {
			{0.5773503, 0.5773503,0.5773503},
			{0, 0.7071068, -0.7071068},
			{0.8164966, -0.4082483,-0.4082483}
		};

		private static double[,] adPitasHSItoRGB = new double[,] {
			{0.5773503, 0, 0.8164966},
			{ 0.5773503, 0.7071068, -0.4082483},
			{0.5773503, -0.7071068, -0.4082483}
		};

		private static double[,] adPrattRGBtoHSI = new double[,] {
			{0.3333333,0.3333333,0.3333333},
			{-0.4082483,-0.4082483,0.8164966},
			{0.4082483,-0.4082483,0}
		};

		private static double[,] adPrattHSItoRGB = new double[,] {
			{1, -t, 3*t },
			{1, -t, -3*t},
			{1, 2*t, 0 }
		};

		private static double t = 1 / Math.Sqrt(6);

		public static double[, ,] NormalizeRGB(Bitmap bmIn)
		{
			double[, ,] adNormal = new double[bmIn.Width, bmIn.Height, 3];
			int k;
			#region Image pointer setup
			BitmapData srcBitData;
			Rectangle rectSrc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			srcBitData = bmIn.LockBits(rectSrc, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			int strideSrc = srcBitData.Stride;

			IntPtr srcPtr = srcBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			int srcBytePP = imageBPP / 8;
			#endregion Image pointer setup

			unsafe
			{
				byte* sptr = (byte*)(void*)srcPtr;
				for (int j = 0; j < bmIn.Height; j++)
				{
					k = j * strideSrc;
					for (int i = 0; i < bmIn.Width; i++)
					{
						adNormal[i, j, 0] = sptr[k + i * srcBytePP] / 255.0;
						if (srcBytePP > 1)
						{
							adNormal[i, j, 1] = sptr[k + i * srcBytePP + 1] / 255.0;
							adNormal[i, j, 2] = sptr[k + i * srcBytePP + 2] / 255.0;
						} //end if
						else //make them all the same (simulate gray level)
						{
							adNormal[i, j, 1] = sptr[k + i * srcBytePP] / 255.0;
							adNormal[i, j, 2] = sptr[k + i * srcBytePP] / 255.0;
						}
					} //end for
				} //end for
			} //end unsafe

			bmIn.UnlockBits(srcBitData);
			return adNormal;
		}

		public static Bitmap UnNormalizeRGB(double[, ,] adNormal, int nWidth, int nHeight, bool doClamp, PixelFormat pixelFormat)
		{
			Bitmap bmIn = new Bitmap(nWidth, nHeight, pixelFormat);
			int bytePP = (int)pixelFormat / 8;
			int k;
			int l;
			#region Image pointer setup
			BitmapData srcBitData;
			Rectangle rectSrc = new Rectangle(0, 0, bmIn.Width, bmIn.Height);
			srcBitData = bmIn.LockBits(rectSrc, ImageLockMode.ReadOnly, bmIn.PixelFormat);
			int strideSrc = srcBitData.Stride;

			IntPtr srcPtr = srcBitData.Scan0;
			int imageBPP = Image.GetPixelFormatSize(bmIn.PixelFormat);
			int srcBytePP = imageBPP / 8;
			#endregion Image pointer setup


			if (doClamp)
			{
				unsafe
				{
					byte* dptr = (byte*)(void*)srcPtr;

					for (int j = 0; j < nHeight; j++)
					{
						k = j * strideSrc;
						for (int i = 0; i < nWidth; i++)
						{
							l = i * srcBytePP;
							dptr[k + l] = (byte)Math.Max(Math.Min(255.0, adNormal[i, j, 0] * 255.0), 0);
							if (bytePP > 1)
							{
								dptr[k + l + 1] = (byte)Math.Max(Math.Min(255.0, adNormal[i, j, 1] * 255.0), 0);
								dptr[k + l + 2] = (byte)Math.Max(Math.Min(255.0, adNormal[i, j, 2] * 255.0), 0);
								if (bytePP >= 4)
								{
									dptr[k + l + 3] = 255;
								} //end if
							} //end if

						} //end for
					} //end for

				} //end unsafe
			} //end if
			else //normalize by min/max
			{
				//TODO: this should be done in a YIQ/HSI format.
				double[] dMin = new double[] { double.MaxValue, double.MaxValue, double.MaxValue };
				double[] dMax = new double[] { double.MinValue, double.MinValue, double.MinValue };
				for (int j = 0; j < nHeight; j++)
				{
					for (int i = 0; i < nWidth; i++)
					{
						//p = (p – minP)/max-minP)*height;
						//q = (q – minQ)/(maxQ-minQ)*width;
						if (adNormal[i, j, 0] > dMax[0])
							dMax[0] = adNormal[i, j, 0];
						if (adNormal[i, j, 0] < dMin[0])
							dMin[0] = adNormal[i, j, 0];
						if (srcBytePP > 1)
						{
							if (adNormal[i, j, 1] > dMax[1])
								dMax[1] = adNormal[i, j, 1];
							if (adNormal[i, j, 1] < dMin[1])
								dMin[1] = adNormal[i, j, 1];
							if (adNormal[i, j, 2] > dMax[2])
								dMax[2] = adNormal[i, j, 2];
							if (adNormal[i, j, 2] < dMin[2])
								dMin[2] = adNormal[i, j, 2];
						} //end if
					} //end for
				} //end for
				unsafe
				{
					byte* dptr = (byte*)(void*)srcPtr;
					double dMaxSubMin0 = dMax[0] - dMin[0];
					double dMaxSubMin1 = dMax[1] - dMin[1];
					double dMaxSubMin2 = dMax[2] - dMin[2];

					for (int j = 0; j < nHeight; j++)
					{
						k = j * strideSrc;
						for (int i = 0; i < nWidth; i++)
						{
							l = i * srcBytePP;
							dptr[k + l] = (byte)(((adNormal[i, j, 0] - dMin[0]) / dMaxSubMin0) * 255.0);
							if (bytePP > 1)
							{
								dptr[k + l + 1] = (byte)(((adNormal[i, j, 1] - dMin[1]) / dMaxSubMin1) * 255.0);
								dptr[k + l + 2] = (byte)(((adNormal[i, j, 2] - dMin[2]) / dMaxSubMin2) * 255.0);
								if (bytePP >= 4)
								{
									dptr[k + l + 3] = 255;
								} //end if
							} //end if

						} //end for
					} //end for

				} //end unsafe
			}//end else


			bmIn.UnlockBits(srcBitData);
			return bmIn;


		}//end UnNormalizeRGB

		//assumes normalized RGB values.
		public static double[, ,] YIQConversion(double[, ,] adImage, int nWidth, int nHeight, bool isRGBtoYIQ)
		{
			double[, ,] adOut = new double[nWidth, nHeight,3];

			if (isRGBtoYIQ)
			{
				for (int j = 0; j < nHeight; j++)
				{
					for (int i = 0; i < nWidth; i++)
					{
						adOut[i, j, 0] = Math.Max(0, Math.Min(1.0, adImage[i, j, 0] * adRGBtoYIQ[0, 0] + adImage[i, j, 1] * adRGBtoYIQ[0, 1] + adImage[i, j, 2] * adRGBtoYIQ[0, 2]));
						adOut[i, j, 1] = Math.Max(-.5957, Math.Min(.5957, adImage[i, j, 0] * adRGBtoYIQ[1, 0] + adImage[i, j, 1] * adRGBtoYIQ[1, 1] + adImage[i, j, 2] * adRGBtoYIQ[1, 2]));
						adOut[i, j, 2] = Math.Max(-.5226, Math.Min(.5226, adImage[i, j, 0] * adRGBtoYIQ[2, 0] + adImage[i, j, 1] * adRGBtoYIQ[2, 1] + adImage[i, j, 2] * adRGBtoYIQ[2, 2]));
					} //end for
				} //end for
			} //end if

			else
			{ //YIQ to RGB
				for (int j = 0; j < nHeight; j++)
				{
					for (int i = 0; i < nWidth; i++)
					{
						adOut[i, j, 0] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adYIQtoRGB[0, 0] + adImage[i, j, 1] * adYIQtoRGB[0, 1] + adImage[i, j, 2] * adYIQtoRGB[0, 2]));
						adOut[i, j, 1] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adYIQtoRGB[1, 0] + adImage[i, j, 1] * adYIQtoRGB[1, 1] + adImage[i, j, 2] * adYIQtoRGB[1, 2]));
						adOut[i, j, 2] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adYIQtoRGB[2, 0] + adImage[i, j, 1] * adYIQtoRGB[2, 1] + adImage[i, j, 2] * adYIQtoRGB[2, 2]));
					} //end for
				} //end for
			} //end else

			return adOut;
		}//end YIQConversion

		//actually calculates {I,V1,V2}. No need to go to H,S
		public static double[, ,] HSIConversion(double[, ,] adImage, int nWidth, int nHeight, bool isRGBtoHSI, bool isPitas)
		{

			double[, ,] adOut = new double[nWidth, nHeight, 3];
			//TODO fix min/max clamping
			if (isRGBtoHSI)
			{
				if (isPitas)
				{
					for (int j = 0; j < nHeight; j++)
					{
						for (int i = 0; i < nWidth; i++)
						{
							adOut[i, j, 0] = adImage[i, j, 0] * adPitasRGBtoHSI[0, 0] + adImage[i, j, 1] * adPitasRGBtoHSI[0, 1] + adImage[i, j, 2] * adPitasRGBtoHSI[0, 2];
							adOut[i, j, 1] = adImage[i, j, 0] * adPitasRGBtoHSI[1, 0] + adImage[i, j, 1] * adPitasRGBtoHSI[1, 1] + adImage[i, j, 2] * adPitasRGBtoHSI[1, 2];
							adOut[i, j, 2] = adImage[i, j, 0] * adPitasRGBtoHSI[2, 0] + adImage[i, j, 1] * adPitasRGBtoHSI[2, 1] + adImage[i, j, 2] * adPitasRGBtoHSI[2, 2];
						} //end for
					} //end for
				} //end if
				else
				{
					for (int j = 0; j < nHeight; j++)
					{
						for (int i = 0; i < nWidth; i++)
						{
							adOut[i, j, 0] = adImage[i, j, 0] * adPrattRGBtoHSI[0, 0] + adImage[i, j, 1] * adPrattRGBtoHSI[0, 1] + adImage[i, j, 2] * adPrattRGBtoHSI[0, 2];
							adOut[i, j, 1] = adImage[i, j, 0] * adPrattRGBtoHSI[1, 0] + adImage[i, j, 1] * adPrattRGBtoHSI[1, 1] + adImage[i, j, 2] * adPrattRGBtoHSI[1, 2];
							adOut[i, j, 2] = adImage[i, j, 0] * adPrattRGBtoHSI[2, 0] + adImage[i, j, 1] * adPrattRGBtoHSI[2, 1] + adImage[i, j, 2] * adPrattRGBtoHSI[2, 2];
						} //end for
					} //end for
				} //end else
			} //end if
			else
			{
				if (isPitas)
				{
					for (int j = 0; j < nHeight; j++)
					{
						for (int i = 0; i < nWidth; i++)
						{

							adOut[i, j, 0] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adPitasHSItoRGB[0, 0] + adImage[i, j, 1] * adPitasHSItoRGB[0, 1] + adImage[i, j, 2] * adPitasHSItoRGB[0, 2]));
							adOut[i, j, 1] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adPitasHSItoRGB[1, 0] + adImage[i, j, 1] * adPitasHSItoRGB[1, 1] + adImage[i, j, 2] * adPitasHSItoRGB[1, 2]));
							adOut[i, j, 2] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adPitasHSItoRGB[2, 0] + adImage[i, j, 1] * adPitasHSItoRGB[2, 1] + adImage[i, j, 2] * adPitasHSItoRGB[2, 2]));
						} //end for
					} //end for
				} //end if
				else
				{
					for (int j = 0; j < nHeight; j++)
					{
						for (int i = 0; i < nWidth; i++)
						{

							adOut[i, j, 0] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adPrattHSItoRGB[0, 0] + adImage[i, j, 1] * adPrattHSItoRGB[0, 1] + adImage[i, j, 2] * adPrattHSItoRGB[0, 2]));
							adOut[i, j, 1] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adPrattHSItoRGB[1, 0] + adImage[i, j, 1] * adPrattHSItoRGB[1, 1] + adImage[i, j, 2] * adPrattHSItoRGB[1, 2]));
							adOut[i, j, 2] = Math.Max(0.0, Math.Min(1.0, adImage[i, j, 0] * adPrattHSItoRGB[2, 0] + adImage[i, j, 1] * adPrattHSItoRGB[2, 1] + adImage[i, j, 2] * adPrattHSItoRGB[2, 2]));
						} //end for
					} //end for
				} //end else
			} //end else

			return adOut;

		}//end HSIConversion

	}
}
